package com.lixixi.tingshu.common.aspect;

import com.alibaba.fastjson.JSONObject;
import com.lixixi.tingshu.common.anno.SetCache;
import com.lixixi.tingshu.common.constant.RedisConstant;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

@Component
@Aspect
public class CacheAspect {
	@Autowired
	RedisTemplate<String, String> redisTemplate;

	@Autowired
	RedissonClient redissonClient;

	@Around("@annotation(com.lixixi.tingshu.common.anno.SetCache) && execution(* com.lixixi.tingshu.*.service.impl.*.*(..))")
	public Object around(ProceedingJoinPoint point) throws Throwable {
		//获取参数
		Object[] args = point.getArgs();
		//获取注解
		MethodSignature methodSignature = (MethodSignature) point.getSignature();
		SetCache setCache = methodSignature.getMethod().getAnnotation(SetCache.class);
		//获取返回值类型
		Class returnType = methodSignature.getReturnType();
		//先判断是否设立缓存
		if (Boolean.TRUE.equals(setCache.need()))
		{
			//需要设立缓存
			//获取缓存key
			String key = setCache.prefix() + Arrays.asList(args);
			try
			{
				//查询redis中缓存
				String cache = redisTemplate.opsForValue().get(key);
				if (null != cache)
				{
					//缓存存在直接返回
					return JSONObject.parseObject(cache, returnType);
				}
				//缓存获取不到则直接查询数据库,为防止大量请求同时打入数据库对其使用分布式锁进行加锁
				//设置分布式锁唯一标识
				String lockKey = key + ":lock";
				//使用redisson获取锁
				RLock lock = redissonClient.getLock(lockKey);
				//尝试加锁
				//最多尝试获取锁持续1s,锁有效时间为1s
				boolean absent = lock.tryLock(RedisConstant.CACHE_LOCK_EXPIRE_PX1, RedisConstant.CACHE_LOCK_EXPIRE_PX2, TimeUnit.SECONDS);
				if (Boolean.TRUE.equals(absent))
				{
					try
					{
						//查询数据
						Object object = point.proceed(args);

						if (object != null)
						{
							//将其存入缓存
							redisTemplate.opsForValue().set(key, JSONObject.toJSONString(object), RedisConstant.ALBUM_TIMEOUT, TimeUnit.SECONDS);
						}
						return object;
					} catch (Exception e)
					{
						//数据库宕机,发送消息,且打印日志
						throw new RuntimeException(e);
					} finally{
						//释放锁
						lock.unlock();
					}
				} else
				{
					//获取不到锁则休眠300ms随后自旋查询缓存
					try
					{
						Thread.sleep(300);
						return around(point);
					} catch (InterruptedException e)
					{
						throw new RuntimeException(e);
					}
				}
			} catch (RuntimeException e)
			{
				throw new RuntimeException(e);
			}
		}
		//不需要设立缓存或出现异常则暂时直接查询数据库
		return point.proceed(args);
	}

}
